package com.pccw.odc.roo.addon.querydsl;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.*;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MutableClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.*;
import org.springframework.roo.support.util.Assert;
import org.springframework.roo.support.util.FileCopyUtils;
import org.springframework.roo.support.util.TemplateUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Element;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Implementation of operations this add-on offers.
 *
 * @since 1.1
 */
@Component // Use these Apache Felix annotations to register your commands class in the Roo container
@Service
public class QuerydslOperationsImpl implements QuerydslOperations {

    /**
     * MetadataService offers access to Roo's metadata model, use it to retrieve any available metadata by its MID
     */
    @Reference
    private MetadataService metadataService;

    /**
     * Use the PhysicalTypeMetadataProvider to access information about a physical type in the project
     */
    @Reference
    private PhysicalTypeMetadataProvider physicalTypeMetadataProvider;

    /**
     * Use ProjectOperations to install new dependencies, plugins, properties, etc into the project configuration
     */
    @Reference
    private ProjectOperations projectOperations;

    /**
     * Use TypeLocationService to find types which are annotated with a given annotation in the project
     */
    @Reference
    private TypeLocationService typeLocationService;

    @Reference
    private FileManager fileManager;

    /**
     * {@inheritDoc}
     */
    public boolean isCommandAvailable() {
        // Check if a project has been created
        return projectOperations.isProjectAvailable();
    }

    /**
     * {@inheritDoc}
     */
    public void annotateType(JavaType javaType) {
        // Use Roo's Assert type for null checks
        Assert.notNull(javaType, "Java type required");

        // Retrieve metadata for the Java source type the annotation is being added to
        String id = physicalTypeMetadataProvider.findIdentifier(javaType);
        if (id == null) {
            throw new IllegalArgumentException("Cannot locate source for '" + javaType.getFullyQualifiedTypeName() + "'");
        }

        // Obtain the physical type and itd mutable details
        PhysicalTypeMetadata physicalTypeMetadata = (PhysicalTypeMetadata) metadataService.get(id);
        Assert.notNull(physicalTypeMetadata, "Java source code unavailable for type " + PhysicalTypeIdentifier.getFriendlyName(id));

        // Obtain physical type details for the target type
        PhysicalTypeDetails physicalTypeDetails = physicalTypeMetadata.getMemberHoldingTypeDetails();
        Assert.notNull(physicalTypeDetails, "Java source code details unavailable for type " + PhysicalTypeIdentifier.getFriendlyName(id));

        // Test if the type is an MutableClassOrInterfaceTypeDetails instance so the annotation can be added
        Assert.isInstanceOf(MutableClassOrInterfaceTypeDetails.class, physicalTypeDetails, "Java source code is immutable for type " + PhysicalTypeIdentifier.getFriendlyName(id));
        MutableClassOrInterfaceTypeDetails mutableTypeDetails = (MutableClassOrInterfaceTypeDetails) physicalTypeDetails;

        // Test if the annotation already exists on the target type
        if (MemberFindingUtils.getAnnotationOfType(mutableTypeDetails.getAnnotations(), new JavaType("javax.persistence.Entity")) == null) {

            // Create JavaType instance for the add-ons trigger annotation
            JavaType rooRooQuerydsl = new JavaType("javax.persistence.Entity");

            // Create Annotation metadata
            AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(rooRooQuerydsl);

            // Add annotation to target type
            mutableTypeDetails.addTypeAnnotation(annotationBuilder.build());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void annotateAll() {
        // Use the TypeLocationService to scan project for all types with a specific annotation
        for (JavaType type : typeLocationService.findTypesWithAnnotation(new JavaType("org.springframework.roo.addon.javabean.RooJavaBean"))) {
            annotateType(type);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setup() {
        // Install the add-on Google code repository needed to get the annotation
        List<Dependency> dependencies = new ArrayList<Dependency>();
        List<Property> properties = new ArrayList<Property>();
        List<Plugin> plugins = new ArrayList<Plugin>();
        List<Repository> repositories = new ArrayList<Repository>();

        // Install dependencies defined in external XML file
        for (Element dependencyElement : XmlUtils.findElements("/configuration/querydsl/dependencies/dependency", XmlUtils.getConfiguration(getClass()))) {
            dependencies.add(new Dependency(dependencyElement));
        }
        for (Element propertieElement : XmlUtils.findElements("/configuration/querydsl/properties/*", XmlUtils.getConfiguration(getClass()))) {
            properties.add(new Property(propertieElement));
        }
        for (Element pluginElement : XmlUtils.findElements("/configuration/querydsl/plugins/plugin", XmlUtils.getConfiguration(getClass()))) {
            plugins.add(new Plugin(pluginElement));
        }
        for (Element respositoryElement : XmlUtils.findElements("/configuration/querydsl/repositories/repository", XmlUtils.getConfiguration(getClass()))) {
            repositories.add(new Repository(respositoryElement));
        }

        // Add all new dependencies to pom.xml
        projectOperations.addDependencies(dependencies);
        projectOperations.addRepositories(repositories);
        projectOperations.addBuildPlugins(plugins);
        for (Property property : properties) {
            projectOperations.addProperty(property);
        }
        copyType("querydsl/QuerydslGenerate.java");

    }

    private void copyType(String destinationFile) {
        JavaPackage topLevelPackage = projectOperations.getProjectMetadata().getTopLevelPackage();
        String theFile = projectOperations.getPathResolver().getIdentifier(Path.SRC_MAIN_JAVA, topLevelPackage.getFullyQualifiedPackageName().replaceAll("\\.",
                "/") + "/" + destinationFile);
        if (!fileManager.exists(theFile)) {
            InputStream templateInputStream = TemplateUtils.getTemplate(QuerydslOperationsImpl.class,  destinationFile + "-template");

            try {
                // Read template and insert the user's package
                String input = FileCopyUtils.copyToString(new InputStreamReader(templateInputStream));
                input = input.replace("__TOP_LEVEL_PACKAGE__", topLevelPackage.getFullyQualifiedPackageName());


                // Output the file for the user

                MutableFile mutableFile = fileManager.createFile(theFile);
                FileCopyUtils.copy(input.getBytes(), mutableFile.getOutputStream());
            } catch (IOException ioe) {
                throw new IllegalStateException("Unable to create '" + destinationFile + "'", ioe);
            }
        }
    }
}